#region

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;

#endregion

namespace MediaController.Extractor
{
    public static class JunctionPoint
    {
        
        private const int ERROR_INVALID_REPARSE_DATA = 0x1128;
        private const int ERROR_NOT_A_REPARSE_POINT = 0x1126;
        private const int ERROR_REPARSE_ATTRIBUTE_CONFLICT = 0x1127;
        private const int ERROR_REPARSE_TAG_INVALID = 0x1129;
        private const int ERROR_REPARSE_TAG_MISMATCH = 0x112a;
        private const int FSCTL_DELETE_REPARSE_POINT = 0x900ac;
        private const int FSCTL_GET_REPARSE_POINT = 0x900a8;
        private const int FSCTL_SET_REPARSE_POINT = 0x900a4;
        private const uint IO_REPARSE_TAG_MOUNT_POINT = 0xa0000003;
        private const string NonInterpretedPathPrefix = @"\??\";

        
        public static void Create(string junctionPoint, string targetDir, bool overwrite)
        {
            targetDir = Path.GetFullPath(targetDir);
            if (!Directory.Exists(targetDir))
            {
                throw new IOException("Target path does not exist or is not a directory.");
            }
            if (Directory.Exists(junctionPoint))
            {
                if (!overwrite)
                {
                    throw new IOException("Directory already exists and overwrite parameter is false.");
                }
            }
            else
            {
                Directory.CreateDirectory(junctionPoint);
            }
            using (SafeFileHandle handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite))
            {
                byte[] bytes = Encoding.Unicode.GetBytes(@"\??\" + Path.GetFullPath(targetDir));
                var structure = new ReparseDataBuffer();
                structure.ReparseTag = 0xa0000003;
                structure.ReparseDataLength = (ushort) (bytes.Length + 12);
                structure.SubstituteNameOffset = 0;
                structure.SubstituteNameLength = (ushort) bytes.Length;
                structure.PrintNameOffset = (ushort) (bytes.Length + 2);
                structure.PrintNameLength = 0;
                structure.PathBuffer = new byte[0x3ff0];
                Array.Copy(bytes, structure.PathBuffer, bytes.Length);
                IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(structure));
                try
                {
                    int num2;
                    Marshal.StructureToPtr(structure, ptr, false);
                    if (
                        !DeviceIoControl(handle.DangerousGetHandle(), 0x900a4, ptr, bytes.Length + 20, IntPtr.Zero, 0,
                                         out num2, IntPtr.Zero))
                    {
                        ThrowLastWin32Error("Unable to create junction point.");
                    }
                }
                finally
                {
                    Marshal.FreeHGlobal(ptr);
                }
            }
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr CreateFile(string lpFileName, EFileAccess dwDesiredAccess, EFileShare dwShareMode,
                                                IntPtr lpSecurityAttributes, ECreationDisposition dwCreationDisposition,
                                                EFileAttributes dwFlagsAndAttributes, IntPtr hTemplateFile);

        public static void Delete(string junctionPoint)
        {
            if (!Directory.Exists(junctionPoint))
            {
                if (File.Exists(junctionPoint))
                {
                    throw new IOException("Path is not a junction point.");
                }
            }
            else
            {
                using (SafeFileHandle handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite))
                {
                    var structure = new ReparseDataBuffer();
                    structure.ReparseTag = 0xa0000003;
                    structure.ReparseDataLength = 0;
                    structure.PathBuffer = new byte[0x3ff0];
                    IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(structure));
                    try
                    {
                        int num2;
                        Marshal.StructureToPtr(structure, ptr, false);
                        if (
                            !DeviceIoControl(handle.DangerousGetHandle(), 0x900ac, ptr, 8, IntPtr.Zero, 0, out num2,
                                             IntPtr.Zero))
                        {
                            ThrowLastWin32Error("Unable to delete junction point.");
                        }
                    }
                    finally
                    {
                        Marshal.FreeHGlobal(ptr);
                    }
                    try
                    {
                        Directory.Delete(junctionPoint);
                    }
                    catch (IOException exception)
                    {
                        throw new IOException("Unable to delete junction point.", exception);
                    }
                }
            }
        }

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode, IntPtr InBuffer,
                                                   int nInBufferSize, IntPtr OutBuffer, int nOutBufferSize,
                                                   out int pBytesReturned, IntPtr lpOverlapped);

        public static bool Exists(string path)
        {
            if (!Directory.Exists(path))
            {
                return false;
            }
            using (SafeFileHandle handle = OpenReparsePoint(path, EFileAccess.GenericAll))
            {
                return (InternalGetTarget(handle) != null);
            }
        }

        public static string GetTarget(string junctionPoint)
        {
//k-2147483648
            using (SafeFileHandle handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericAll))
            {
                string target = InternalGetTarget(handle);
                if (target == null)
                {
                    throw new IOException("Path is not a junction point.");
                }
                return target;
            }
        }

        private static string InternalGetTarget(SafeFileHandle handle)
        {
            string str2;
            int cb = Marshal.SizeOf(typeof (ReparseDataBuffer));
            IntPtr outBuffer = Marshal.AllocHGlobal(cb);
            try
            {
                int num2;
                if (
                    !DeviceIoControl(handle.DangerousGetHandle(), 0x900a8, IntPtr.Zero, 0, outBuffer, cb, out num2,
                                     IntPtr.Zero))
                {
                    if (Marshal.GetLastWin32Error() == 0x1126)
                    {
                        return null;
                    }
                    ThrowLastWin32Error("Unable to get information about junction point.");
                }
                var reparse_data_buffer =
                    (ReparseDataBuffer) Marshal.PtrToStructure(outBuffer, typeof (ReparseDataBuffer));
                if (reparse_data_buffer.ReparseTag != 0xa0000003)
                {
                    return null;
                }
                string str = Encoding.Unicode.GetString(reparse_data_buffer.PathBuffer,
                                                        reparse_data_buffer.SubstituteNameOffset,
                                                        reparse_data_buffer.SubstituteNameLength);
                if (str.StartsWith(@"\??\"))
                {
                    str = str.Substring(@"\??\".Length);
                }
                str2 = str;
            }
            finally
            {
                Marshal.FreeHGlobal(outBuffer);
            }
            return str2;
        }

        private static SafeFileHandle OpenReparsePoint(string reparsePoint, EFileAccess accessMode)
        {
            var handle =
                new SafeFileHandle(
                    CreateFile(reparsePoint, accessMode, EFileShare.Write | EFileShare.Delete | EFileShare.Read,
                               IntPtr.Zero, ECreationDisposition.OpenExisting,
                               EFileAttributes.OpenReparsePoint | EFileAttributes.BackupSemantics, IntPtr.Zero), true);
            if (Marshal.GetLastWin32Error() != 0)
            {
                ThrowLastWin32Error("Unable to open reparse point.");
            }
            return handle;
        }

        private static void ThrowLastWin32Error(string message)
        {
            throw new IOException(message, Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()));
        }

        // Nested Types

        #region Nested type: ECreationDisposition

        private enum ECreationDisposition : uint
        {
            CreateAlways = 2,
            New = 1,
            OpenAlways = 4,
            OpenExisting = 3,
            TruncateExisting = 5
        }

        #endregion

        #region Nested type: EFileAccess

        [Flags]
        private enum EFileAccess : uint
        {
            GenericAll = 0x10000000,
            GenericExecute = 0x20000000,
            GenericRead = 0x80000000,
            GenericWrite = 0x40000000
        }

        #endregion

        #region Nested type: EFileAttributes

        [Flags]
        private enum EFileAttributes : uint
        {
            Archive = 0x20,
            BackupSemantics = 0x2000000,
            Compressed = 0x800,
            DeleteOnClose = 0x4000000,
            Device = 0x40,
            Directory = 0x10,
            Encrypted = 0x4000,
            FirstPipeInstance = 0x80000,
            Hidden = 2,
            NoBuffering = 0x20000000,
            Normal = 0x80,
            NotContentIndexed = 0x2000,
            Offline = 0x1000,
            OpenNoRecall = 0x100000,
            OpenReparsePoint = 0x200000,
            Overlapped = 0x40000000,
            PosixSemantics = 0x1000000,
            RandomAccess = 0x10000000,
            Readonly = 1,
            ReparsePoint = 0x400,
            SequentialScan = 0x8000000,
            SparseFile = 0x200,
            System = 4,
            Temporary = 0x100,
            Write_Through = 0x80000000
        }

        #endregion

        #region Nested type: EFileShare

        [Flags]
        private enum EFileShare : uint
        {
            Delete = 4,
            None = 0,
            Read = 1,
            Write = 2
        }

        #endregion

        #region Nested type: ReparseDataBuffer

        [StructLayout(LayoutKind.Sequential)]
        private struct ReparseDataBuffer
        {
            public uint ReparseTag;
            public ushort ReparseDataLength;
            public ushort Reserved;
            public ushort SubstituteNameOffset;
            public ushort SubstituteNameLength;
            public ushort PrintNameOffset;
            public ushort PrintNameLength;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3ff0)] public byte[] PathBuffer;
        }

        #endregion
    }
}